/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.modules.makefile; import java.io.File; import java.io.IOException; import java.text.*; import java.util.*; import org.openide.compiler.*; import org.openide.compiler.Compiler; import org.openide.execution.NbProcessDescriptor; import org.openide.filesystems.*; import org.openide.util.*; /** Compiler group. * Runs one makefile at a time. * * @author Jaroslav Tulach, Jesse Glick */ public class MakefileCompilerGroup extends ExternalCompilerGroup { /** The target to use. */ private String target; /** The path to the makefile we are running. */ private String makefileName; /** Add the (one) compiler to the group. * @param c must be a make compiler * @throws IllegalArgumentException if not */ public void add (Compiler c) throws IllegalArgumentException { if (! (c instanceof MakefileCompiler)) throw new IllegalArgumentException (); super.add (c); MakefileCompiler mc = (MakefileCompiler) c; target = mc.getTarget (); } /** Create the process to run make. * @param desc the command template * @param files must be one filename * @throws IOException if not, or any other problem * @return the process */ protected Process createProcess (NbProcessDescriptor desc, String[] files) throws IOException { if (files.length != 1) throw new IOException ("Cannot be applied to >1 files"); makefileName = files[0]; return desc.exec (new Format (makefileName, target)); } /** All real listeners to changes. */ private Set listeners = new HashSet (); /** A dummy listener which will delegate. */ private CompilerListener myselfListener = null; /** Add a compiler listener but trap and delegate error events. * This permits us to fix up otherwise missing file pointers * where needed. * @param l the listener to add */ public synchronized void addCompilerListener (CompilerListener l) { listeners.add (l); if (myselfListener == null) { myselfListener = new CompilerListener () { public void compilerError (ErrorEvent ev) { //System.err.println("ErrorEvent: file=" + ev.getFile () + " message=" + ev.getMessage () + " referenceText=" + ev.getReferenceText ()); String message = ev.getMessage (); if (ev.getFile () == null && message != null) { //System.err.println("will try to correct null file"); // This is a real hack. Basically we do not wish to fiddle with the // regular expressions all over again. However, the external compiler // implementation currently includes the partial file match information // in a specially formatted message string constructed from the file // name that it knows of, the line and column numbers, and the original // message. So we are just reversing this message and trying to extract // the original file name, then trying to find it ourselves relative to // the directory the makefile is in (which is often how the file name will // be reported, rather than an absolute name as is customary for regular // compilers since this is what is passed to them by the IDE). String dummyMessage; try { dummyMessage = NbBundle.getBundle (ExternalCompiler.class).getString ("MSG_Unknown_file"); } catch (MissingResourceException mre) { System.err.println("WARNING: could not find original MSG_Unknown_file key;"); System.err.println("compilation errors from Makefiles may not be hyperlinked"); dummyMessage = NbBundle.getBundle (MakefileCompilerGroup.class).getString ("MSG_Unknown_file"); } //System.err.println("dummyMessage=" + dummyMessage); MessageFormat format = new MessageFormat (dummyMessage); try { Object[] parse = format.parse (message); if (parse.length >= 4 && (parse[0] instanceof String) && (parse[3] instanceof String)) { String file = (String) parse[0]; //System.err.println("file=" + file); final String origMessage = (String) parse[3]; //System.err.println("origMessage=" + origMessage); // Calculate probable file name based on makefile name and stated name; // should handle e.g. ..\..\foo\Bar.java and such things File real = new File (new File (makefileName).getParentFile (), file.replace ('/', File.separatorChar)); try { real = real.getCanonicalFile (); } catch (IOException ioe) { ioe.printStackTrace (); } final String realS = real.toString (); //System.err.println("realS=" + realS); // Now look for it in filesystems which add one directory to the classpath: Enumeration fss = FileSystemCapability.COMPILE.fileSystems (); FOUNDYA: while (fss.hasMoreElements ()) { final FileSystem fs = (FileSystem) fss.nextElement (); //System.err.println("fs=" + fs.getDisplayName ()); try { final ErrorEvent[] fixed = new ErrorEvent[] { null }; final ErrorEvent origEvent = ev; fs.prepareEnvironment (new FileSystem.Environment () { public void addClassPath (String classPathElement) { File rootPath = new File (classPathElement); if (rootPath.isDirectory ()) { try { rootPath = rootPath.getCanonicalFile (); } catch (IOException ioe) { ioe.printStackTrace (); } String rootPathS = rootPath.toString (); //System.err.println("rootPathS=" + rootPathS); if (realS.startsWith (rootPathS)) { String relative = realS.substring (rootPathS.length ()); if (relative.startsWith (File.separator)) relative = relative.substring (1); //System.err.println("relative=" + relative); FileObject fo = fs.findResource (relative.replace (File.separatorChar, '/')); if (fo != null) { // About time! :-) //System.err.println("fixing! fo=" + fo); fixed[0] = new ErrorEvent (origEvent.getCompilerGroup (), fo, origEvent.getLine (), origEvent.getColumn (), origMessage, origEvent.getReferenceText ()); } } } } }); if (fixed[0] != null) { ev = fixed[0]; break FOUNDYA; } } catch (EnvironmentNotSupportedException ense) { //System.err.println("got: " + ense); } } } else { //System.err.println("Parse result did not start/end with a String or was not four elements"); //System.err.println("Parse result:"); //for (int i = 0; i < parse.length; i++) // System.err.println("\t" + parse[i]); } } catch (ParseException pe) { //System.err.println("Could not parse the message"); } } synchronized (MakefileCompilerGroup.this) { Iterator it = listeners.iterator (); while (it.hasNext ()) ((CompilerListener) it.next ()).compilerError (ev); } } public void compilerProgress (ProgressEvent ev) { //System.err.println("ProgressEvent: file=" + ev.getFile () + " task=" + ev.getTask ()); synchronized (MakefileCompilerGroup.this) { Iterator it = listeners.iterator (); while (it.hasNext ()) ((CompilerListener) it.next ()).compilerProgress (ev); } } }; super.addCompilerListener (myselfListener); } } /** Remove a listener. * @param l the listener to remove */ public synchronized void removeCompilerListener (CompilerListener l) { listeners.remove (l); } /** Formats files and directory tags. */ public static class Format extends MapFormat { /** Tag for the makefile basename. */ public static final String TAG_MAKEFILE = "makefile"; /** Tag for the makefile's containing directory. */ public static final String TAG_DIRECTORY = "directory"; /** Tag for the makefile's full path name. */ public static final String TAG_DIRECTORY_AND_MAKEFILE = "directory_and_makefile"; /** Tag for the makefile target. */ public static final String TAG_TARGET = "target"; /** generated */ private static final long serialVersionUID = 449845585043666525L; /** Creates the format. * @param file the makefile * @param target the make target */ public Format (String file, String target) { super (new HashMap (4)); File f = new File (file); getMap ().put (TAG_MAKEFILE, f.getName ()); getMap ().put (TAG_DIRECTORY, f.getParentFile ().getAbsolutePath ()); getMap ().put (TAG_DIRECTORY_AND_MAKEFILE, f.getAbsolutePath ()); getMap ().put (TAG_TARGET, target); } } }